package org.xmap.ui.control;

import java.util.Enumeration;
import java.util.Vector;
import javax.microedition.lcdui.Canvas;
import javax.microedition.lcdui.Graphics;
import org.xmap.app.Env;
import org.xmap.ui.UIUtil;
import org.xmap.ui.base.Window;
import org.xmap.ui.base.Event;
import org.xmap.ui.base.Rect;
import org.xmap.ui.ViewManager;

/**
 * <p>文本框控件，可以放置大量的文字，并且会自动换行，支持纵向滚动条。</p>
 * <p>文本框右侧有5个像素滚动条，这5个像素在文本换行的时候将被算上，也就是说，换行宽度将会是区域的宽度减去5个像素。</p>
 * @author Zealot
 * @version 1.0
 */
public final class TextArea extends Window {

    /**
     * 文本区域的拉伸模式。
     */
    protected int scretchMode;
    /**
     * 存储文本的向量。
     */
    private Vector text;
    /**
     * 存储显示文本的向量。
     */
    private Vector rowText;
    /**
     * 文本的颜色。
     */
    private int textColor;
    /**
     * 边框的颜色。
     */
    private int lineColor;
    /**
     * 滚动条边框的颜色。
     */
    private int strollbarLineColor;
    /**
     * 滚动条的颜色。
     */
    private int strollbarColor;
    /**
     * 是否显示滚动条
     */
    private boolean showStrollbar;
    /**
     * 是否显示边框。
     */
    private boolean drawRect;
    /**
     * 文本的对齐方式。
     */
    private int align;
    /**
     * 是否是透明，如果透明，将不会显示背景颜色。
     */
    private boolean transparent;
    /**
     * 行高，用于排版。
     */
    private int rowHeight;
    /**
     * 当前区域能显示几行。
     */
    private int disCount;
    /**
     * 当前绘制文字的第一行在向量中的序号。
     */
    private int drawBase;
    /**
     * 文本的纵向偏移，为了让文本居中显示。
     */
    private int disOffset;
    /**
     * 有效的宽度，除去右侧滚动条的宽度。
     */
    private int effectiveWidth;
    /**
     * 滚动条的y坐标。
     */
    private int strollY;
    /**
     * 滚动条的高度。
     */
    private int strollHeight;
    /**
     * 触笔选中滚动条的时候，上下移动触笔strollRowHeight个像素，将导致文本区域移动。
     */
    private int strollRowHeight;
    /**
     * 左侧除去滚动条的区域。
     */
    private Rect leftRect;

    /**
     * 指定文本区域左上角位置、宽度和高度的构造函数。
     * @param leftUpX int 文本区域左上角横坐标。
     * @param leftUpY int 文本区域左上角纵坐标。
     * @param width int 文本区域宽度。
     * @param height int 文本区域高度。
     */
    public TextArea(int leftUpX, int leftUpY, int width, int height) {
        this(leftUpX, leftUpY, width, height, Window.ORIENT_LEFT_UP);
    }

    /**
     * 指定锚点坐标、锚点类型、文本区域宽度、高度的构造函数。
     * @param x int 锚点横坐标。
     * @param y int 锚点纵坐标。
     * @param width int 文本区域宽度。
     * @param height int 文本区域高度。
     * @param orient int 锚点类型。
     */
    public TextArea(int x, int y, int width, int height, int orient) {
        super(x, y, width, height, orient);
        this.scretchMode = 0;
        this.text = new Vector(16);
        this.rowText = new Vector(16);
        this.textColor = 0xff000000;
        this.lineColor = 0x00000000;
        this.strollbarLineColor = 0xFF1BA0D8;
        this.strollbarColor = 0xFFFCA432;
        this.showStrollbar = true;
        this.drawRect = false;
        this.align = Window.ALIGN_LEFT;
        this.transparent = false;
        this.rowHeight = Env.instance.defaultFontHeight;
        this.disCount = 0;
        this.drawBase = 0;
        this.disOffset = 0;
        this.leftRect = new Rect(this.getRect());
        this.calcuateView();
    }

    /**
     * 设置文本区域的窗口区域。
     * @param leftUpX int 文本区域左上角横坐标。
     * @param leftUpY int 文本区域左上角纵坐标。
     * @param width int 文本区域宽度。
     * @param height int 文本区域高度。
     */
    public void setRect(int leftUpX, int leftUpY, int width, int height) {
        super.setRect(leftUpX, leftUpY, width, height);
        this.calcuateView();
    }

    /**
     * 设置文本区域的拉伸模式。
     * @param scretchMode int 拉伸模式。
     */
    public void setScretchMode(int scretchMode) {
        if (scretchMode <= 3) {
            this.scretchMode = scretchMode;
            if (this.scretchMode != 0) {
                this.sizeChanged(Env.instance.screenWidth, Env.instance.screenHeight);
            }
        }
    }

    /**
     * 获得文本区域的拉伸模式。
     * @return int 拉伸模式。
     */
    public int getScretchMode() {
        return this.scretchMode;
    }

    /**
     * 屏幕发生改变的通知。
     * @param width int 新的屏幕宽度。
     * @param height int 新的屏幕高度。
     */
    public void sizeChanged(int width, int height) {
        boolean calcuateView = false;
        if ((this.scretchMode & Window.SCRETCH_ROW) > 0) { // 横向扩展
            this.getRect().leftUp.x = 0;
            if (this.getRect().width != width) {
                this.getRect().width = width;
                calcuateView = true;
            }
        }
        if ((this.scretchMode & Window.SCRETCH_COLUMN) > 0) { // 纵向扩展
            if (this.owner != null) {
                this.getRect().leftUp.y = 0;
                if (this.getRect().height != this.owner.getClientRect().height) {
                    this.getRect().height = this.owner.getClientRect().height;
                    calcuateView = true;
                }
            }
        }
        if (calcuateView) {
            this.calcuateView();
        }
    }

    /**
     * 向文本区域添加文本。
     * @param text String 文本信息。
     */
    public void addText(String text) {
        this.addText(text, false);
    }

    /**
     * 向文本区域添加文本。
     * @param text String 文本信息。
     * @param reArrange boolean 是否为重新排版。
     */
    private void addText(String text, boolean reArrange) {
        if (text != null) {
            try {
                char[] allText = text.toCharArray();
                int length = 0;
                int base = 0;
                for (int i = 0; i < allText.length; i++) {
                    char c = allText[i];
                    int charLen = Env.instance.defaultFont.charWidth(c);
                    length += charLen;
                    if (length > (this.effectiveWidth)) {
                        length = charLen;
                        RowText rt = new RowText(text, base, i);
                        base = i;
                        this.rowText.addElement(rt);
                    }
                }
                RowText rt = new RowText(text, base, allText.length);
                this.rowText.addElement(rt);
                this.calcuateStrollbar();
                if (!reArrange) {
                    this.text.addElement(text);
                }
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
        ViewManager.instance.requestRepaint();
    }

    /**
     * 向文本区域添加文本数组。
     * @param text String[] 文本数组。
     */
    public void addText(String[] text) {
        if (text != null) {
            for (int i = 0; i < text.length; i++) {
                this.addText(text[i], false);
            }
        }
    }

    /**
     * 以异步的方式添加文本。
     * @param text String 文本信息。
     */
    public void addTextAsynchronized(String text) {
        if (text != null) {
            new Arranger(text).start();
        }
    }

    /**
     * 以异步的方式添加文本数组。
     * @param text String[] 文本数组。
     */
    public void addTextAsynchronized(String[] text) {
        if (text != null) {
            new Arranger(text).start();
        }
    }

    /**
     * 获得指定行的文本信息。
     * @param row int 指定行。
     * @return String 文本信息。
     */
    public String getTextAt(int row) {
        if (row < this.rowText.size()) {
            return (String) this.rowText.elementAt(row);
        } else {
            return null;
        }
    }

    /**
     * 删除指定行的文本信息。
     * @param row int 行信息。
     */
    public void deleteTextAt(int row) {
        if (row < this.rowText.size()) {
            this.rowText.elementAt(row);
            if (row >= this.drawBase) {
                if (this.drawBase > 0) {
                    if (this.rowText.size() - this.drawBase < this.disCount) {
                        this.drawBase--;
                    }
                }
            }
            this.calcuateStrollbar();
        }
    }

    /**
     * 删除所有的文本信息。
     */
    public void delAll() {
        this.text.removeAllElements();
        this.rowText.removeAllElements();
        this.drawBase = 0;
        this.calcuateStrollbar();
    }

    /**
     * 设置文字的颜色。
     * @param color int 文字的颜色。
     */
    public void setTextColor(int color) {
        this.textColor = color;
    }

    /**
     * 获得文字的颜色。
     * @return int 文字的颜色。
     */
    public int getTextColor() {
        return this.textColor;
    }

    /**
     * 设置边框的颜色。
     * @param lineColor int 边框的颜色。
     */
    public void setLineColor(int lineColor) {
        this.lineColor = lineColor;
    }

    /**
     * 获得边框的颜色。
     * @return int 边框的颜色。
     */
    public int getLineColor() {
        return this.lineColor;
    }

    /**
     * 设置滚动条边框的颜色。
     * @param strollbarLineColor int 边框的颜色。
     */
    public void setStrollbarLineColor(int strollbarLineColor) {
        this.strollbarLineColor = strollbarLineColor;
    }

    /**
     * 获得滚动条边框的颜色。
     * @return int 滚动条边框的颜色。
     */
    public int getStrollbarLineColor() {
        return this.strollbarLineColor;
    }

    /**
     * 设置滚动条的颜色。
     * @param strollbarColor int 滚动条的颜色。
     */
    public void setStrollbarColor(int strollbarColor) {
        this.strollbarColor = strollbarColor;
    }

    /**
     * 获得滚动条的颜色。
     * @return int 滚动条的颜色。
     */
    public int getStrollbarColor() {
        return this.strollbarColor;
    }

    /**
     * 设置是否显示滚动条。
     * @param showStrollbar 是否显示滚动条。
     */
    public void setShowStrollbar(boolean showStrollbar) {
        this.showStrollbar = showStrollbar;
    }

    /**
     * 获得是否显示滚动条。
     * @return 是否显示滚动条。
     */
    public boolean getShowStrollbar() {
        return this.showStrollbar;
    }

    /**
     * 设置是否显示边框。
     * @param drawRect boolean 是否显示边框。
     */
    public void setDrawRect(boolean drawRect) {
        this.drawRect = drawRect;
    }

    /**
     * 获得是否显示边框。
     * @return boolean 是否显示边框。
     */
    public boolean getDrawRect() {
        return this.drawRect;
    }

    /**
     * 设置文本区域的对齐方式。
     * @param align int 对齐方式。
     */
    public void setAlign(int align) {
        switch (align) {
            case Window.ALIGN_RIGHT:
                this.align = Window.ALIGN_RIGHT;
                break;
            case Window.ALIGN_CENTER:
                this.align = Window.ALIGN_CENTER;
                break;
            default:
                this.align = Window.ALIGN_LEFT;
                break;
        }
        ViewManager.instance.requestRepaint();
    }

    /**
     * 获得文本区域的对齐方式。
     * @return int 对齐方式。
     */
    public int getAlign() {
        return this.align;
    }

    /**
     * 设置当前文本区域是否为透明，透明的意思是不用背景颜色填充背景。
     * @param transparent boolean 是否透明。
     */
    public void setTransparent(boolean transparent) {
        this.transparent = transparent;
    }

    /**
     * 获得当前文本区域是否为透明。
     * @return boolean 是否透明。
     */
    public boolean getTransparent() {
        return this.transparent;
    }

    /**
     * 设置文本区域行高。
     * @param rowHeight int 行高。
     */
    public void setRowHeight(int rowHeight) {
        this.rowHeight = rowHeight;
        this.calcuateView();
    }

    /**
     * 获得当前的行高。
     * @return int 当前行高。
     */
    public int getRowHeight() {
        return this.rowHeight;
    }

    /**
     * 获得当前文字的总行数。
     * @return int 当前文字的总行数。
     */
    public int getTextRow() {
        return this.rowText.size();
    }

    /**
     * 获得当前文本区域可以显示的最大行数。
     * @return int 最大行数。
     */
    public int getRows() {
        return this.disCount;
    }

    /**
     * 绘制函数。
     * @param g Graphics 图形句柄。
     */
    public void draw(Graphics g) {
//        if (!this.show) {
//            return;
//        }
        g.setFont(Env.instance.defaultFont);
        if (!this.transparent) {
            g.setColor(this.bgColor);
            g.fillRect(0, 0, this.getRect().width, this.getRect().height);
        }
        g.translate(0, this.disOffset);
        g.setColor(this.textColor);
        int y = 0;
        int num = 0;
        Enumeration e = this.rowText.elements();
        while (e.hasMoreElements()) {
            RowText rt = (RowText) e.nextElement();
            String s = rt.source.substring(rt.begin, rt.end);
            if (num >= this.drawBase && num < (this.drawBase + this.disCount)) {
                y += this.rowHeight;
                switch (this.align) {
                    case Window.ALIGN_LEFT:
                        UIUtil.draw(g, s, 1, y - (this.rowHeight >> 1), Graphics.LEFT, Graphics.VCENTER);
                        break;
                    case Window.ALIGN_CENTER:
                        UIUtil.draw(g, s, this.effectiveWidth >> 1, y - (this.rowHeight >> 1),
                                Graphics.HCENTER, Graphics.VCENTER);
                        break;
                    case Window.ALIGN_RIGHT:
                        UIUtil.draw(g, s, this.effectiveWidth - 1, y - (this.rowHeight >> 1),
                                Graphics.RIGHT, Graphics.VCENTER);
                        break;
                }
            }
            num++;
        }
        g.translate(0, -this.disOffset);
        if (this.showStrollbar) {
            g.setColor(this.strollbarLineColor);
            g.drawRect(this.effectiveWidth, 0, 4, this.getRect().height - 1);
            //        int countInStrollBar = this.text.size();
            //        int strollBegin = this.drawBase;
            //        int strollEnd = this.drawBase + this.disCount;
            //
            //        g.drawString("cnt:" + countInStrollBar, 0, 0, Graphics.LEFT | Graphics.TOP);
            //        g.drawString("beg:" + strollBegin, 0, 20, Graphics.LEFT | Graphics.TOP);
            //        g.drawString("end:" + strollEnd, 0, 40, Graphics.LEFT | Graphics.TOP);
            g.setColor(this.strollbarColor);
            if (this.rowText.size() != 0) {
                g.fillRect(this.effectiveWidth + 1, 1 + strollY, Window.STROLL_WIDTH - 2, strollHeight);
            } else {
                g.fillRect(this.effectiveWidth + 1, 1, Window.STROLL_WIDTH - 2, this.getRect().height - 2);
            }
        }
        if (this.drawRect) {
            g.setColor(this.lineColor);
            g.drawRect(0, 0, this.getRect().width - 1, this.getRect().height - 1);
        }
    }

    /**
     * TextArea控件处理上下按键消息。
     * @param flag boolean 是按下还是抬起。
     * @param keyCode int 按键代码。
     * @param gameAction int 动作代码。
     * @return boolean 是否处理该事件。
     */
    public boolean keyEvent(boolean flag, int keyCode, int gameAction) {
//        if (!this.show || !this.enable) {
//            return false;
//        }
        switch (gameAction) {
            case Canvas.UP:
                if (flag) {
                    this.strollUp();
                }
                return true;
            case Canvas.DOWN:
                if (flag) {
                    this.strollDown();
                }
                return true;
        }
        return false;
    }

    /**
     * 触笔事件处理函数，在TextArea区域内的触笔事件都被处理。
     * @param ev Event 触笔事件。
     * @return boolean 是否处理该事件。
     */
    public boolean pointerEvent(Event ev) {
//        if (!this.show || !this.enable) {
//            return false;
//        }
        boolean result = false;
        switch (ev.type) {
            case Event.POINTER_PRESSED:
                if (this.leftRect.collideWith(ev.param1, ev.param2)) { // 选中左侧区域
//                    this.isPointerSelected = true;
                    super.setPointerSelected(true);
                    this.pointerPressedY = ev.param2; // 记录触笔的垂直位置
                    result = true;
                } else if (this.getRect().collideWith(ev.param1, ev.param2)) { // 选中右侧滚动条
//                    this.isPointerSelected = true;
                    super.setPointerSelected(true);
                    this.pointerPressedY = ev.param2; // 记录触笔的垂直位置
                    if (ev.param2 < this.strollY) { // 计算选中的位置，然后判断向上还是向下
                        this.strollUp();
                    } else if (ev.param2 > this.strollY + this.strollHeight) {
                        this.strollDown();
                    }
                    result = true;
                }
                break;
            case Event.POINTER_RELEASED:
                if (super.getPointerSelected()) {
                    super.setPointerSelected(false);
//                    this.isPointerSelected = false;
                    result = true;
                }
                break;
            case Event.POINTER_DRAGGED:
                if (super.getPointerSelected()) {
                    if (this.leftRect.collideWith(ev.param1, ev.param2)) { // 选中左侧区域
                        int y = ev.param2 - this.pointerPressedY;
                        if (Math.abs(y) > (this.rowHeight >> 1)) { // 触笔上下移动
                            this.pointerPressedY = ev.param2;
                            if (y > 0) {
                                this.strollUp();
                            } else {
                                this.strollDown();
                            }
                        }
                        result = true;
                    } else if (this.getRect().collideWith(ev.param1, ev.param2)) { // 选中右侧滚动条
                        if (ev.param2 > this.strollY && ev.param2 < this.strollY + this.strollHeight) {
                            int y = ev.param2 - this.pointerPressedY;
                            if (Math.abs(y) > this.strollRowHeight) { // 触笔上下移动
                                this.pointerPressedY = ev.param2;
                                if (y > 0) {
                                    this.strollDown();
                                } else {
                                    this.strollUp();
                                }
                            }
                        }
                        result = true;
                    }
                }
                break;
        }
        return result;
    }

    /**
     * 重新计算行数和纵向偏移，当行高或区域高度发生变化的时候，需要调用这个行数。
     */
    private void calcuateView() {
        if (this.effectiveWidth != this.getRect().width - Window.STROLL_WIDTH) {
            this.effectiveWidth = this.getRect().width - Window.STROLL_WIDTH;
            if (this.effectiveWidth < 0) {
                this.effectiveWidth = 0;
            }
            // 重新排版
            this.rowText.removeAllElements();
            for (int i = 0; i < this.text.size(); i++) {
                String s = (String) this.text.elementAt(i);
                this.addText(s, true);
            }
        }
        this.disCount = this.getRect().height / this.rowHeight;
        this.disOffset = (this.getRect().height % this.rowHeight) >> 1;
        if (this.drawBase > 0) {
            int withoutBaseSize = this.rowText.size() - this.drawBase;
            if (this.disCount > withoutBaseSize) {
                this.drawBase -= (this.disCount - withoutBaseSize);
                if (this.drawBase < 0) {
                    this.drawBase = 0;
                }
            }
        }
        this.calcuateStrollbar();
        this.leftRect.setRect(this.getRect());
        this.leftRect.width = this.effectiveWidth;
        ViewManager.instance.requestRepaint();
    }

    /**
     * 计算滚动条的y坐标和高度。
     */
    private void calcuateStrollbar() {
        int countInStrollBar = this.rowText.size();
        if (countInStrollBar != 0) {
            this.strollY = (this.getRect().height - 2) * this.drawBase / countInStrollBar;
            if (this.drawBase == this.rowText.size() - this.disCount) { // 当滚动条到最下面的时候，避免计算误差
                this.strollHeight = this.getRect().height - 2 - this.strollY;
            } else {
                this.strollHeight = (this.getRect().height - 2) * Math.min(this.disCount, countInStrollBar) / countInStrollBar;
            }
        } else {
            this.strollY = 0;
            this.strollHeight = this.getRect().height - 2;
        }
        if (this.disCount > 0) {
            this.strollRowHeight = this.strollHeight / this.disCount;
        } else {
            this.strollRowHeight = this.getRect().height - 2;
        }
        ViewManager.instance.requestRepaint();
    }

    /**
     * 选中栏向上平移。
     */
    private void strollUp() {
        if (this.drawBase > 0) {
            this.drawBase--;
        }
        this.calcuateStrollbar();
    }

    /**
     * 选中栏向下平移。
     */
    private void strollDown() {
        if (this.drawBase < (this.rowText.size() - this.disCount)) {
            this.drawBase++;
        }
        this.calcuateStrollbar();
    }

    /**
     * <p>一个RowText对象代表显示一行。</p>
     * @author Zealot
     * @version 1.0
     */
    final class RowText {

        /**
         * 源字符串。
         */
        public String source;
        /**
         * 初始位置。
         */
        public int begin;
        /**
         * 结束位置。
         */
        public int end;

        /**
         * 指定源字符串、初始位置、结束位置的构造函数。
         * @param source String 源字符串。
         * @param begin int 初始位置。
         * @param end int 结束位置。
         */
        public RowText(String source, int begin, int end) {
            this.source = source;
            this.begin = begin;
            this.end = end;
            String t = source.substring(begin, end);
            System.out.println(t);
        }
    }

    /**
     * <p>排版类，用于异步排版。</p>
     * @author Zealot
     * @version 1.0
     */
    final class Arranger extends Thread {

        /**
         * 需要排版的文字数组。
         */
        private String[] texts;

        /**
         * 指定需要排版的文字的构造函数。
         * @param text String 需要排版的文字。
         */
        Arranger(String text) {
            if (text != null) {
                this.texts = new String[]{text};
            }
        }

        /**
         * 指定需要排版的文字数组的构造函数。
         * @param texts String[] 需要排版的文字数组。
         */
        Arranger(String[] texts) {
            this.texts = texts;
        }

        /**
         * 排版函数，由于为异步排版，所以启动一个新的线程来执行该操作。
         */
        public void run() {
            if (this.texts != null) {
                for (int i = 0; i < this.texts.length; i++) {
                    TextArea.this.addText(this.texts[i], false);
                }
            }
        }
    }
}
